/*
 * Copyright 2014 Semidata. All rights reserved.
 */
package com.semidata.trp.report.service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import com.semidata.rtd.core.log.Logger;
import com.semidata.rtd.core.service.Service;
import com.semidata.trp.report.config.Config;
import com.semidata.trp.report.data.ReportData;
import com.semidata.trp.report.merge.MonitorDataMerge;
import com.semidata.trp.report.merge.UserDataMerge;
import com.semidata.trp.report.utils.DatabaseUtils;
import com.semidata.trp.report.utils.SqlUtils;

/**
 * @author shewei@semidata.com, She Wei
 * 
 */

public class ReportService implements Service {
	private static final Config config = Config.getConfig();
	private boolean quit = false;
	private Thread thread = null;

	@Override
	public void start() {

		thread = new Thread(new Runnable() {

			@Override
			public void run() {
				while (!quit) {
					work();
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}

		});

		thread.start();
	}

	@Override
	public void stop() {
		quit = true;
		if (thread != null)
			try {
				thread.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
	}

	public void work() {

		Date start = getStartDate();
		if (start == null)
			return;

		String month = checkMonth(start);

		if (month != null) {
			String day = checkDay(start, month);
			if (day == null) {
				mergeMonth(start, month);
			} else {
				String hour = checkHour(day);
				if (hour == null) {
					mergeDay(day);
				} else {
					String minute = checkMinute(hour);
					if (minute == null) {
						mergeHour(hour);
					} else {
						mergeMinute(minute);
					}
				}
			}
		}

		String week = checkWeek(start);
		if (week != null) {
			mergeWeek(week);
		}
	}

	private Date getStartDate() {

		String[] files = new File(config.getReportDirMonitor()).list();
		if (files == null || files.length == 0)
			return null;
		TreeSet<String> set = new TreeSet<>();
		set.addAll(Arrays.asList(files));

		for (String file : set) {
			DateFormat format = new SimpleDateFormat("yyyyMMdd");
			try {
				return format.parse(file);
			} catch (ParseException e) {
			}
		}

		return null;
	}

	private File getDir(File dir, String date) {

		if (date.length() == 6) {
			return new File(dir, date);
		} else if (date.length() == 8) {
			dir = new File(dir, date.substring(0, 6));
			return new File(dir, date);
		} else if (date.length() == 10) {
			dir = new File(dir, date.substring(0, 6));
			dir = new File(dir, date.substring(0, 8));
			return new File(dir, date);
		} else if (date.length() == 12) {
			dir = new File(dir, date.substring(0, 6));
			dir = new File(dir, date.substring(0, 8));
			dir = new File(dir, date.substring(0, 10));
			return new File(dir, date);
		} else {
			return null;
		}
	}

	private String checkMonth(Date startDay) {

		Calendar start = Calendar.getInstance();
		start.setTime(startDay);
		Calendar end = Calendar.getInstance();

		return check(new File(config.getReportDirMonth()), start, end,
		        Calendar.MONTH);
	}

	private String checkWeek(Date startDay) {
		Calendar start = Calendar.getInstance();
		start.setTime(startDay);
		Calendar end = Calendar.getInstance();
		end.add(Calendar.DAY_OF_MONTH, -7);

		return check(new File(config.getReportDirWeek()), start, end,
		        Calendar.DAY_OF_MONTH);
	}

	private String checkDay(Date startDay, String month) {

		Calendar start = Calendar.getInstance();

		if (new SimpleDateFormat("yyyyMM").format(startDay).equals(month)) {
			start.setTime(startDay);
		} else {
			try {
				start.setTime(new SimpleDateFormat("yyyyMM").parse(month));
			} catch (ParseException e) {
				return null;
			}
		}

		Calendar end = (Calendar) start.clone();
		end.add(Calendar.MONTH, 1);

		return check(new File(config.getReportDirDay()), start, end,
		        Calendar.DAY_OF_MONTH);
	}

	private String checkHour(String day) {
		Calendar start = Calendar.getInstance();

		try {
			start.setTime(new SimpleDateFormat("yyyyMMdd").parse(day));
		} catch (ParseException e) {
			return null;
		}

		Calendar end = (Calendar) start.clone();
		end.add(Calendar.DAY_OF_MONTH, 1);
		return check(new File(config.getReportDirHour()), start, end,
		        Calendar.HOUR_OF_DAY);
	}

	private String checkMinute(String hour) {
		Calendar start = Calendar.getInstance();

		try {
			start.setTime(new SimpleDateFormat("yyyyMMddHH").parse(hour));
		} catch (ParseException e) {
			e.printStackTrace();
		}

		Calendar end = (Calendar) start.clone();
		end.add(Calendar.HOUR, 1);
		return check(new File(config.getReportDirMinute()), start, end,
		        Calendar.MINUTE);
	}

	private String check(File root, Calendar start, Calendar end, int field) {
		String pattern = null;

		switch (field) {

		case Calendar.MONTH:
			pattern = "yyyyMM";
			break;
		case Calendar.DAY_OF_MONTH:
			pattern = "yyyyMMdd";
			break;
		case Calendar.HOUR_OF_DAY:
			pattern = "yyyyMMddHH";
			break;
		case Calendar.MINUTE:
			pattern = "yyyyMMddHHmm";
			break;
		default:
			return null;
		}

		for (; start.before(end); start.add(field, 1)) {
			String name = new SimpleDateFormat(pattern).format(start.getTime());
			File file = getDir(root, name);
			if (!file.exists())
				return name;
		}
		return null;
	}

	private void output(File destDir, ReportData reportData, boolean complete) {
		if (!destDir.exists())
			destDir.mkdirs();
		if (reportData != null) {
			try {
				FileOutputStream out = new FileOutputStream(new File(destDir,
				        "report"));
				out.write(reportData.toString().getBytes());
				out.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		try {
			FileOutputStream out = new FileOutputStream(new File(destDir,
			        complete ? "COMPLETE" : "INCOMPLETE"));
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private List<File> getSrcDirs(File root, Calendar start, Calendar end,
	        int field) {
		List<File> srcDirs = new ArrayList<>();

		String pattern = null;

		switch (field) {

		case Calendar.MONTH:
			pattern = "yyyyMM";
			break;
		case Calendar.DAY_OF_MONTH:
			pattern = "yyyyMMdd";
			break;
		case Calendar.HOUR_OF_DAY:
			pattern = "yyyyMMddHH";
			break;
		case Calendar.MINUTE:
			pattern = "yyyyMMddHHmm";
			break;
		default:
			return null;
		}

		for (; start.before(end); start.add(field, 1)) {
			srcDirs.add(getDir(root,
			        new SimpleDateFormat(pattern).format(start.getTime())));
		}
		return srcDirs;

	}

	private boolean checkComplete(List<File> srcDirs) {
		boolean complete = true;
		for (File dir : srcDirs) {

			File file = new File(dir, "COMPLETE");
			if (!file.exists()) {
				complete = false;
				break;
			}
		}
		return complete;
	}

	private File getTmpDir() {

		File dir = new File(config.getReportDirTmp());
		if (dir.exists()) {
			for (File file : dir.listFiles()) {
				file.delete();
			}
			dir.delete();
		}

		if (!dir.mkdirs()) {
			throw new RuntimeException("bad tmp dir, please check");
		}

		return dir;
	}

	private void completeMerge(File tmpDir, File destDir) {

		File parent = destDir.getParentFile();
		if (!parent.exists()) {
			if (!parent.mkdirs()) {
				throw new RuntimeException("bad dir, please check");
			}
		}

		try {
			Files.move(tmpDir.toPath(), destDir.toPath(),
			        StandardCopyOption.ATOMIC_MOVE);
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("bad dir, please check");
		}
	}

	private void mergeMonth(Date startDay, String month) {
		Logger.log("DEBUG", "merge month: " + month);
		Map<String, Map<Integer, Integer>> businessMap;
		try {
			businessMap = SqlUtils.getBusinessMap();
		} catch (Exception e) {
			e.printStackTrace();
			return;
		}

		Calendar start = Calendar.getInstance();
		if (new SimpleDateFormat("yyyyMM").format(startDay).equals(month)) {
			start.setTime(startDay);
		} else {
			try {
				start.setTime(new SimpleDateFormat("yyyyMM").parse(month));
			} catch (ParseException e) {
				return;
			}
		}
		Calendar end = (Calendar) start.clone();
		end.add(Calendar.MONTH, 1);

		List<File> srcDirs = getSrcDirs(new File(config.getReportDirDay()),
		        start, end, Calendar.DAY_OF_MONTH);

		UserDataMerge merge = new UserDataMerge();
		ReportData reportData = new ReportData();

		boolean complete = merge.merge(config.getThreads(), srcDirs, null,
		        checkComplete(srcDirs), reportData, businessMap);

		File tmpDir = getTmpDir();
		File destDir = getDir(new File(config.getReportDirMonth()), month);
		output(tmpDir, reportData, complete);
		try {
			DatabaseUtils.writeMonthlyReport(reportData, month);
		} catch (Exception e) {
			return;
		}
		completeMerge(tmpDir, destDir);

	}

	private void mergeWeek(String week) {
		Calendar start = Calendar.getInstance();
		try {
			start.setTime(new SimpleDateFormat("yyyyMMdd").parse(week));
		} catch (ParseException e) {
			return;
		}

		Calendar end = (Calendar) start.clone();
		end.add(Calendar.DAY_OF_MONTH, 7);

		List<File> srcDirs = getSrcDirs(new File(config.getReportDirDay()),
		        start, end, Calendar.DAY_OF_MONTH);

		for (File dir : srcDirs) {
			if (!dir.exists())
				return;
		}
		Map<String, Map<Integer, Integer>> businessMap;
		try {
			businessMap = SqlUtils.getBusinessMap();
		} catch (Exception e) {
			e.printStackTrace();
			return;
		}
		
		Logger.log("DEBUG", "merge week: " + week);

		UserDataMerge merge = new UserDataMerge();
		ReportData reportData = new ReportData();

		boolean complete = merge.merge(config.getThreads(), srcDirs, null,
		        checkComplete(srcDirs), reportData, businessMap);

		File tmpDir = getTmpDir();
		File destDir = getDir(new File(config.getReportDirWeek()), week);
		output(tmpDir, reportData, complete);
		try {
			DatabaseUtils.writeWeeklyReport(reportData, week);
		} catch (Exception e) {
			return;
		}
		completeMerge(tmpDir, destDir);
	}

	private void mergeDay(String day) {
		Logger.log("DEBUG", "merge day: " + day);
		Map<String, Map<Integer, Integer>> businessMap;
		try {
			businessMap = SqlUtils.getBusinessMap();
		} catch (Exception e) {
			e.printStackTrace();
			return;
		}
		Calendar start = Calendar.getInstance();
		try {
			start.setTime(new SimpleDateFormat("yyyyMMdd").parse(day));
		} catch (ParseException e) {
			return;
		}
		Calendar end = (Calendar) start.clone();
		end.add(Calendar.DAY_OF_MONTH, 1);

		List<File> srcDirs = getSrcDirs(new File(config.getReportDirHour()),
		        start, end, Calendar.HOUR_OF_DAY);

		UserDataMerge merge = new UserDataMerge();
		ReportData reportData = new ReportData();
		File tmpDir = getTmpDir();
		File destDir = getDir(new File(config.getReportDirDay()), day);

		boolean complete = merge.merge(config.getThreads(), srcDirs, tmpDir,
		        checkComplete(srcDirs), reportData, businessMap);
		output(tmpDir, reportData, complete);
		try {
			DatabaseUtils.writeDailyReport(reportData, day);
		} catch (Exception e) {
			return;
		}
		completeMerge(tmpDir, destDir);
		if (complete)
			remove(srcDirs);
	}

	private void mergeHour(String hour) {
		Logger.log("DEBUG", "merge hour: " + hour);
		Map<String, Map<Integer, Integer>> businessMap;
		try {
			businessMap = SqlUtils.getBusinessMap();
		} catch (Exception e) {
			e.printStackTrace();
			return;
		}

		Calendar start = Calendar.getInstance();
		try {
			start.setTime(new SimpleDateFormat("yyyyMMddHH").parse(hour));
		} catch (ParseException e) {
			return;
		}
		Calendar end = (Calendar) start.clone();
		end.add(Calendar.HOUR_OF_DAY, 1);

		List<File> srcDirs = getSrcDirs(new File(config.getReportDirMinute()),
		        start, end, Calendar.MINUTE);

		UserDataMerge merge = new UserDataMerge();

		ReportData reportData = new ReportData();
		File tmpDir = getTmpDir();
		File destDir = getDir(new File(config.getReportDirHour()), hour);

		boolean complete = merge.merge(config.getThreads(), srcDirs, tmpDir,
		        checkComplete(srcDirs), reportData, businessMap);
		output(tmpDir, reportData, complete);
		completeMerge(tmpDir, destDir);
		if (complete)
			remove(srcDirs);
	}

	private void mergeMinute(String minute) {
		Logger.log("DEBUG", "merge minute: " + minute);

		MonitorDataMerge merge = new MonitorDataMerge();

		boolean force = false;

		Calendar time = Calendar.getInstance();
		try {
			time.setTime(new SimpleDateFormat("yyyyMMddHHmm").parse(minute));
		} catch (ParseException e) {
			return;
		}

		if (System.currentTimeMillis() - time.getTimeInMillis() > config
		        .getWaitTime()) {
			force = true;
		}

		merge.merge(minute, force);
	}

	private void remove(List<File> srcDirs) {

		for (File dir : srcDirs) {
			File[] files = dir.listFiles();
			for (File file : files) {
				file.delete();
			}
			dir.delete();
		}
	}

	// public static void main(String[] args) {
	// new ReportService().work();
	// }

}
